package org.hackystat.storageservice.server;

import java.util.Map;
import java.util.logging.Logger;

import org.hackystat.storageservice.db.DbManager;
import org.hackystat.storageservice.db.derby.DerbyFacade;
import org.hackystat.storageservice.resources.*;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.logger.RestletLoggerUtil;
import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Restlet;
import org.restlet.Router;
import org.restlet.data.Protocol;

import static org.hackystat.storageservice.server.ServerProperties.CONTEXT_ROOT_KEY;
import static org.hackystat.storageservice.server.ServerProperties.HOSTNAME_KEY;
import static org.hackystat.storageservice.server.ServerProperties.LOGGING_LEVEL_KEY;
import static org.hackystat.storageservice.server.ServerProperties.PORT_KEY;

public class Server extends Application {
	
	  /** Holds the Restlet Component associated with this Server. */
	  private Component component; 
	  
	  /** Holds the host name associated with this Server. */
	  private String hostName;

	  /** Holds the HackystatLogger for the storageservice. */
	  private Logger logger; 

	  /** Holds the StorageServiceProperties instance associated with this storageservice. */
	  private ServerProperties serverProperties;

	  /**
	   * Starts up the web service.  Control-c to exit. 
	   * @param args Ignored. 
	   * @throws Exception if problems occur.
	   */
	  public static void main(final String[] args) throws Exception {
		  Server.newInstance();
	  }

	  /**
	   * Creates a new instance of a DailyProjectData HTTP server, listening on the supplied port.
	   * @return The Server instance created. 
	   * @throws Exception If problems occur starting up this server. 
	   */
	  public static Server newInstance() throws Exception {
	    return newInstance(new ServerProperties());
	  }

	  /**
	   * Creates a new instance of a StorageService HTTP server, listening on the supplied port.
	   * @param  serverProperties The StorageServiceProperties used to initialize this server.
	   * @return The Server instance created. 
	   * @throws Exception If problems occur starting up this server. 
	   */
	  public static Server newInstance(ServerProperties serverProperties) throws Exception {
		Server server = new Server();
	    server.logger = HackystatLogger.getLogger("org.hackystat.storageservice", "storageservice");
	    server.serverProperties = serverProperties;
	    server.hostName = "http://" +
	                      server.serverProperties.get(HOSTNAME_KEY) + 
	                      ":" + 
	                      server.serverProperties.get(PORT_KEY) + 
	                      "/" +
	                      server.serverProperties.get(CONTEXT_ROOT_KEY) +
	                      "/";
	    int port = Integer.valueOf(server.serverProperties.get(PORT_KEY));
	    server.component = new Component();
	    server.component.getServers().add(Protocol.HTTP, port);
	    server.component.getDefaultHost()
	      .attach("/" + server.serverProperties.get(CONTEXT_ROOT_KEY), server);

	    // Set up logging.
	    RestletLoggerUtil.disableLogging();
	    HackystatLogger.setLoggingLevel(server.logger, server.serverProperties.get(LOGGING_LEVEL_KEY));
	    server.logger.warning("Starting StorageService.");
	    server.logger.warning("Host: " + server.hostName);
	    server.logger.info(server.serverProperties.echoProperties());


	    // Now create all of the Resource Managers and store them in the Context.
	    // Ordering constraints: 
	    // - DbManager must precede all resource managers so it can initialize the tables
	    //   before the resource managers add data to them.
	    // - UserManager must be initialized before ProjectManager, since ProjectManager needs
	    //   to know about the Users. 
	    Map<String, Object> attributes = 
	        server.getContext().getAttributes();
	      DbManager dbManager = new DbManager(server);  // we need this later in this method.
	      attributes.put("DbManager", dbManager);
          attributes.put("DerbyFacade", new DerbyFacade(server.serverProperties));
        attributes.put("SensorBaseServer", server);
        attributes.put("ServerProperties", server.serverProperties);
	    // Now let's open for business.
	    server.logger.info("Maximum Java heap size (MB): " + 
	        (Runtime.getRuntime().maxMemory() / 1000000.0));
	    server.component.start();
	    server.logger.warning("StorageService (Version " + getVersion() + ") now running.");
	    return server;
	  }
	  
	  /**
	   * Dispatch to the Projects, SensorData, SensorDataTypes, or Users Resource depending on the URL.
	   * We will authenticate all requests except for registration (users?email={email}).
	   * @return The router Restlet.
	   */
	  @Override
	  public Restlet createRoot() {
	    Router authRouter = new Router(getContext());
	    
	    // SENSORDATATYPES 
	    authRouter.attach("/sensordatatypes", 
	        SensorDataTypesResource.class);
	    authRouter.attach("/sensordatatypes/{sensordatatypename}", 
	        SensorDataTypeResource.class);
	    
	    // USERS
	    authRouter.attach("/users", 
	        UsersResource.class);
	    authRouter.attach("/users/{user}", 
	        org.hackystat.storageservice.resources.alt.UserResource.class);

	    // SENSORDATA 
	    authRouter.attach("/sensordata", 
	        SensorDataResource.class);
	    authRouter.attach("/sensordata/{user}", 
	        UserSensorDataResource.class);
	    authRouter.attach("/sensordata/{user}?sdt={sensordatatype}", 
	        UserSensorDataResource.class);
	    authRouter.attach(
	        "/sensordata/{user}?lastModStartTime={lastModStartTime}&lastModEndTime={lastModEndTime}", 
	        UserSensorDataResource.class);
	    authRouter.attach("/sensordata/{user}/{timestamp}", 
	        UserSensorDataResource.class);
	    
	    // PROJECTS
	    authRouter.attach("/projects", 
	        ProjectsResource.class);
	    String projectUri = "/projects/{user}/{projectname}";
	    authRouter.attach(projectUri,
	        org.hackystat.storageservice.resources.alt.UserProjectResource.class);
	    
	    // PROJECTS SNAPSHOT
	    authRouter.attach(projectUri + "/snapshot" + 
	        "?startTime={startTime}&endTime={endTime}&sdt={sdt}&tool={tool}", 
	        UserProjectSnapshotResource.class);
	    authRouter.attach(projectUri + "/snapshot" + 
	        "?startTime={startTime}&endTime={endTime}&sdt={sdt}",
	        UserProjectSnapshotResource.class);
	    
	    // PROJECTS SUMMARY (note: This url differs from that of the sensorbase) 
	    authRouter.attach("/projects/summary" +  
	        "?startTime={startTime}&endTime={endTime}&userEmailList={userEmailList}&uriPatternList={uriPatternList}&href={href}", 
	        org.hackystat.storageservice.resources.alt.UserProjectSummaryResource.class);

          //************************************************************************
          // numDays are not relevant to the storage service since the ProjectManager of the SensorBase converts a numDay request
          // (MultiDayProjectSummary) into a number of normal startTime-endTime summary requests
          //authRouter.attach(projectUri + "/summary" +
          //   "?startTime={startTime}&numDays={numDays}",
          //    UserProjectSummaryResource.class);
          //************************************************************************

          // PROJECTS SENSORDATA
	    String projectSensorDataUri = projectUri + "/sensordata";
	    authRouter.attach(projectSensorDataUri, 
	        UserProjectSensorDataResource.class);
	    authRouter.attach(projectSensorDataUri +
	     "?startTime={startTime}&endTime={endTime}&startIndex={startIndex}&maxInstances={maxInstances}",
	       UserProjectSensorDataResource.class);
	    authRouter.attach(projectSensorDataUri +  
	        "?startTime={startTime}&endTime={endTime}", 
	        UserProjectSensorDataResource.class);
	    authRouter.attach(projectSensorDataUri +  
	        "?sdt={sdt}&startTime={startTime}&endTime={endTime}&tool={tool}",  
	        UserProjectSensorDataResource.class);
	    authRouter.attach(projectSensorDataUri +  
	        "?sdt={sdt}&startTime={startTime}&endTime={endTime}",  
	        UserProjectSensorDataResource.class);
	    
	    authRouter.attach("/db/table/compress", CompressResource.class);
	    authRouter.attach("/db/table/index", IndexResource.class);
	    authRouter.attach("/db/table/{table}/rowcount", RowCountResource.class);

	    return authRouter;
	  }

	  /**
	   * Returns the version associated with this Package, if available from the jar file manifest.
	   * If not being run from a jar file, then returns "Development". 
	   * @return The version.
	   */
	  public static String getVersion() {
	    String version = 
	      Package.getPackage("org.hackystat.storageservice.server").getImplementationVersion();
	    return (version == null) ? "Development" : version; 
	  }

  /**
   * Returns the ServerProperties instance associated with this server. 
   * @return The server properties.
   */
	public ServerProperties getServerProperties() {
	    return this.serverProperties;
	}

	  
	  /**
	   * Returns the host name associated with this server. 
	   * Example: "http://localhost:9876/sensorbase/"
	   * @return The host name. 
	   */
	  public String getHostName() {
	    return this.hostName;
	  }
}
